iT邦幫忙

2022 iThome 鐵人賽

DAY 13
0

今天來給各位一個我覺得很實用的東西,用過安卓手機的人不知道有沒有遇到需要掃描條碼卻沒有條碼掃描器的困境呢,這時通常會去Google play 商店下載一些看起來很精美,卻充斥一堆廣告的app,但看完今天的文章,你/妳也可以製作出一個屬於自己的QRCode掃描和產生器,重點是沒有廣告喔~~! 聽起來是不是很吸引人阿,那我們開始吧!

QRCode掃描器

  1. 生成UI(含SurfaceView)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="結果:"
        android:textSize="20sp"
        app:layout_constraintBottom_toTopOf="@+id/guideline6"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline2" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.15328467" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.85" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.1" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.5" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.7" />

    <SurfaceView
        android:id="@+id/qrCode_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:visibility="gone"
        app:layout_constraintBottom_toTopOf="@+id/guideline5"
        app:layout_constraintEnd_toStartOf="@+id/guideline3"
        app:layout_constraintStart_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toTopOf="@+id/guideline4" />

    <Button
        android:id="@+id/encoder_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="開始掃描"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/guideline13"
        app:layout_constraintStart_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toTopOf="@+id/guideline6" />

    <TextView
        android:id="@+id/result_txt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        app:layout_constraintBottom_toTopOf="@+id/guideline6"
        app:layout_constraintStart_toEndOf="@+id/result" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline13"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.50121653" />

    <Button
        android:id="@+id/goTo_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="前往"
        android:enabled="false"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/guideline3"
        app:layout_constraintStart_toStartOf="@+id/guideline13"
        app:layout_constraintTop_toTopOf="@+id/guideline6" />

    <Button
        android:id="@+id/constructQrCode_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="產生QRCode"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

因為我有多做額外的功能,如果不需要的可以拉個SurfaceView就行了喔,還有SurfaceView通常會被用來做動畫和畫圖,不過今天我們是用來當我們的掃描器畫面,有興趣的可以去研究看看SurfaceView,挺有趣的,這邊就不多贅述拉。
2. 實例化及建立相機和偵測器

private CameraSource cameraSource;
private BarcodeDetector barcodeDetector;
private SurfaceView qrCode_view;

-------------------------------------------------
qrCode_view = findViewById(R.id.qrCode_view);
//條碼感應器
barcodeDetector = new BarcodeDetector.Builder(this)
        //設定感測模式(QRCode)
        .setBarcodeFormats(Barcode.QR_CODE).build();
//關於相機內容設定
cameraSource = new CameraSource.Builder(this,barcodeDetector)
        .setAutoFocusEnabled(true).build();

//新增SurfaceHolder的CallBack
qrCode_view.getHolder().addCallback(new SurfaceHolder.Callback() {
       @Override
       public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
           //偵測權限
           if(ActivityCompat.checkSelfPermission(getApplicationContext(),
              Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED) {
               Log.e("流程", "surfaceCreated:沒權限" );
               return;
           }
           try{
               Log.e("流程", "surfaceCreated:開啟相機");
               cameraSource.start(surfaceHolder);
           }catch (IOException e){
               e.printStackTrace();
           }
       }
       @Override
       public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder
                                  , int i, int i1, int i2) {
           //相機狀態改變
           Log.e("流程", "surfaceChanged:");
       
       @Override
       public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
           Log.e("流程", "surfaceDestroyed:");
           //相機暫停
           cameraSource.stop();
       }
    });
    //偵測結果
    barcodeDetector.setProcessor(new Detector.Processor<Barcode>(){
        @Override
        public void release(){
        }
        //產生結果時
        @Override
        public void receiveDetections(Detector.Detections<Barcode> detections) {
            //獲取偵測結果
            final SparseArray<Barcode> result=detections.getDetectedItems();
            if(result.size()!=0){
                //回到UI線程,更新UI
                runOnUiThread(()->{
                    result_txt.setText(result.valueAt(0).displayValue);
                    goTo_btn.setEnabled(true);
                });
            }
        }
    });

這裡建立了相機和偵測器,可以想像確實身為一格感測器這些是必要的東西,這裡的關係是相機(CameraSource)包含偵測器(BarcodeDetector),且BarcodeDetector需要設定偵測結果的回傳,而surfaceView負責管理相機的開啟狀態。

基本上這樣就可以開始掃描了,我們來看一下效果。

成果展示

https://ithelp.ithome.com.tw/upload/images/20220804/20139136tBYg7s1u4q.jpg

QRCode生成器

  1. UI生成
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".BarCodeEncoderActivity">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.15" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline8"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.85" />

    <EditText
        android:id="@+id/content_edt"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@null"
        android:ems="10"
        android:hint="請輸入內容"
        android:inputType="textPersonName"
        android:padding="5dp"
        app:layout_constraintBottom_toTopOf="@+id/guideline10"
        app:layout_constraintEnd_toStartOf="@+id/guideline8"
        app:layout_constraintStart_toStartOf="@+id/guideline7"
        app:layout_constraintTop_toTopOf="@+id/guideline9" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline9"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.10" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline10"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.15" />

    <ImageView
        android:id="@+id/result_img"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/guideline12"
        app:layout_constraintEnd_toStartOf="@+id/guideline8"
        app:layout_constraintHorizontal_bias="0.493"
        app:layout_constraintStart_toStartOf="@+id/guideline7"
        app:layout_constraintTop_toTopOf="@+id/guideline11" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline11"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.25" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline12"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.65" />

    <Button
        android:id="@+id/transform_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="轉換"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/guideline8"
        app:layout_constraintStart_toStartOf="@+id/guideline7"
        app:layout_constraintTop_toBottomOf="@+id/result_img" />
</androidx.constraintlayout.widget.ConstraintLayout>

簡單的拉出一個輸入框和負責轉換的按鈕。
2. 建立BarcodeEncoder並加入Button內

private Button transform_btn;
------------------------------------------------------
transform_btn = (Button)findViewById(R.id.transform_btn);
transform_btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        //BarcodeEncoder (將資料負責轉換成Barcode)
        BarcodeEncoder encoder = new BarcodeEncoder();
        try{
            //將輸入的內容轉換,大小不宜太小會模糊
            Bitmap bit = encoder.encodeBitmap(content_edt.getText()
                    .toString(), BarcodeFormat.QR_CODE,250,250);
            //設定至imageView顯示
            result_img.setImageBitmap(bit);
        }catch (WriterException e){
            e.printStackTrace();
        }
    }
});

轉換QRCode就相對比降簡單明瞭,唯一要注意的就是圖片不要用得太小,不然會很模糊相這樣:
https://ithelp.ithome.com.tw/upload/images/20220804/20139136nhOzfUSEhw.jpg

所以注意一下,找一下適當的大小生成喔。

成果展示

https://ithelp.ithome.com.tw/upload/images/20220804/20139136ayYIfTvou5.jpg


上一篇
精華筆記 Day 12 - 手機音量(AUDIO)
下一篇
精華筆記 Day14 -- Thread
系列文
android studio 30天 精華筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言